<?php
/**
 * Prado Portal.
 *
 * @author Steen Rabol <steen.rabol@gmail.com>
 * @link http://www.pradoportal.dk/
 * @copyright Copyright &copy; 2006,2007,2008 Steen Rabol
 * @license http://www.pradoportal.dk
 * @version $Id: XSasl.php 316 2008-11-22 09:15:05Z steen.rabol@gmail.com $
 * @package Pradoportal.Common.XSasl
 *
 */

/**
 * XSasl class file.
 *
 * This file embed tools to perform sasl authentication.
 * Original PHP4 code by Manuel Lemos 2004 <mlemos@acm.org>
 *
 * @author Philippe Gaultier <pgaultier@gmail.com>
 * @version $Revision: 316 $  $Date: 2008-11-22 09:15:05 +0000 (Sat, 22 Nov 2008) $
 * @package XSystem.IO
 * @since 3.0
 */

define("SASL_INTERACT", 2);
define("SASL_CONTINUE", 1);
define("SASL_OK",       0);
define("SASL_FAIL",    -1);
define("SASL_NOMECH",  -4);

define("SASL_PLAIN_STATE_START",    0);
define("SASL_PLAIN_STATE_IDENTIFY", 1);
define("SASL_PLAIN_STATE_DONE",     2);

define("SASL_PLAIN_DEFAULT_MODE",            0);
define("SASL_PLAIN_EXIM_MODE",               1);
define("SASL_PLAIN_EXIM_DOCUMENTATION_MODE", 2);

define("SASL_BASIC_STATE_START",    0);
define("SASL_BASIC_STATE_DONE",     1);

define("SASL_CRAM_MD5_STATE_START",             0);
define("SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE", 1);
define("SASL_CRAM_MD5_STATE_DONE",              2);

define("SASL_DIGEST_STATE_START",             0);
define("SASL_DIGEST_STATE_RESPOND_CHALLENGE", 1);
define("SASL_DIGEST_STATE_DONE",              2);

define("SASL_LOGIN_STATE_START",             0);
define("SASL_LOGIN_STATE_IDENTIFY_USER",     1);
define("SASL_LOGIN_STATE_IDENTIFY_PASSWORD", 2);
define("SASL_LOGIN_STATE_DONE",              3);

define("SASL_NTLM_STATE_START",             0);
define("SASL_NTLM_STATE_IDENTIFY_DOMAIN",   1);
define("SASL_NTLM_STATE_RESPOND_CHALLENGE", 2);
define("SASL_NTLM_STATE_DONE",              3);

/**
 * XSasl class.
 *
 * This class perform sasl authentication using implemented sasl clients.
 * Original PHP4 code by Manuel Lemos 2004 <mlemos@acm.org>
 *
 * @author Philippe Gaultier <pgaultier@gmail.com>
 * @version $Revision: 316 $  $Date: 2008-11-22 09:15:05 +0000 (Sat, 22 Nov 2008) $
 * @package XSystem.IO
 * @since 3.0
 */

class XSasl {
	
	/**
	 * Store the message that is returned when an error occurs.
	 */
	public $error="";
	/**
	 * Store the name of the mechanism that was selected during the
	 * call to the {@link start} function.
	 */
	public $mechanism;
	/**
	 * Let the drivers inform the applications whether responses
	 * need to be encoded
	 */
	public $encodeResponse=true;
	/**
	 * Loaded driver
	 */
	private $_driver;
	/**
	 * Drivers
	 */
	private $_drivers = array(
		"Digest" 	=> "XSaslDriverDigest",
		"CRAM-MD5" 	=> "XSaslDriverCramMd5",
		"LOGIN" 	=> "XSaslDriverLogin",
		"NTLM" 		=> "XSaslDriverNtlm",
		"PLAIN" 	=> "XSaslDriverPlain",
		"Basic" 	=> "XSaslDriverBasic"
	);
	/**
	 * Credentials
	 */
	private $_credentials = array();
	
	/**
	 * Store the value of a credential that may be used by any of
	 * the supported mechanisms to process the authentication messages and
	 * responses.
	 * 
	 * @param string key Specify the name of the credential key.
	 * @param string value Specify the value for the credential.
	 */
	public function setCredential($key, $value)
	{
		$this->_credentials[$key]=$value;
	}
	
	/**
	 * Retrieve the values of one or more credentials to be used by
	 * the authentication mechanism classes.
	 * 
	 * @param array   credentials Reference to an associative array variable with all the
	 *                credentials that are being requested. The function initializes
	 *                this associative array values.
	 * @param array   defaults Associative arrays with default values for credentials
	 *                that may have not been defined.
	 * @param array   interactions Not yet in use. It is meant to provide context
	 *                information to interact with the end user.
	 * @return int    The function may return <code>SASL_CONTINUE</code> if it	
	 *                succeeded, or <code>SASL_NOMECH</code> if it was not possible to 
	 *                retrieve one of the requested credentials.
	 */
	public function getCredentials(&$credentials,$defaults,&$interactions)
	{
		reset($credentials);
		$end=(gettype($key=key($credentials))!="string");
		for(;!$end;)
		{
			if(!isset($this->_credentials[$key]))
			{
				if(isset($defaults[$key]))
					$credentials[$key]=$defaults[$key];
				else
				{
					$this->error="the requested credential ".$key." is not defined";
					return(SASL_NOMECH);
				}
			}
			else
				$credentials[$key]=$this->_credentials[$key];
			next($credentials);
			$end=(gettype($key=key($credentials))!="string");
		}
		return(SASL_CONTINUE);
	}
	
	/**
	 * Process the initial authentication step initializing the
	 * driver class that implements the first of the list of requested
	 * mechanisms that is supported by this SASL client library
	 * implementation.
	 * 
	 * @param array   mechanism Define the list of names of authentication mechanisms
	 *                supported by the that should be tried.
	 * @param string  message Return the initial message that should be sent to the
	 *                server to start the authentication dialog. If this value is
	 *                undefined, no message should be sent to the server.
	 * @param array   interactions Not yet in use. It is meant to provide context
	 *                information to interact with the end user.
	 * @return int    The function may return <code>SASL_CONTINUE</code> if it
	 *                could start one of the requested authentication mechanisms. It
	 *                may return <code>SASL_NOMECH</code> if it was not possible to start
	 *                any of the requested mechanisms. It returns <code>SASL_FAIL</code> or
	 *                other value in case of error.
	 */
	 public function start($mechanisms, &$message, &$interactions)
	 {
		if(strlen($this->error))
			return(SASL_FAIL);
		if(isset($this->_driver) && !is_null($this->_driver))
			return($this->_driver->start($this,$message,$interactions));
		$no_mechanism_error="";
		for($m=0;$m<count($mechanisms);$m++)
		{
			$mechanism=$mechanisms[$m];
			if($this->_drivers[$mechanism]!='')
			{
				/*
				if(!class_exists($this->drivers[$mechanism][0]))
					require(dirname(__FILE__)."/".$this->drivers[$mechanism][1]);
				*/
				$this->_driver=new $this->_drivers[$mechanism];
				if($this->_driver->initialize($this))
				{
					$this->encodeResponse=1;
					$status=$this->_driver->start($this,$message,$interactions);
					switch($status)
					{
						case (SASL_NOMECH):
							unset($this->_driver);
							if(strlen($no_mechanism_error)==0)
								$no_mechanism_error=$this->error;
							$this->error="";
							break;
						case (SASL_CONTINUE):
							$this->mechanism=$mechanism;
							return($status);
						default:
							unset($this->driver);
							$this->error="";
							return($status);
					}
				}
				else
				{
					unset($this->_driver);
					if(strlen($no_mechanism_error)==0)
						$no_mechanism_error=$this->error;
					$this->error="";
				}
			}
		}
		$this->error=(strlen($no_mechanism_error) ? $no_mechanism_error : "it was not requested any of the authentication mechanisms that are supported");
		return(SASL_NOMECH);
	 }
	/**
	 * Process the authentication steps after the initial step,
	 * until the authetication iteration dialog is complete.
	 * 
	 * @param array   mechanism Define the list of names of authentication mechanisms
	 *                supported by the that should be tried.
	 * @param string  message Return the initial message that should be sent to the
	 *                server to start the authentication dialog. If this value is
	 *                undefined, no message should be sent to the server.
	 * @param array   interactions Not yet in use. It is meant to provide context
	 *                information to interact with the end user.
	 * @return int    The function returns <code>SASL_CONTINUE</code> if step was
	 *                processed successfully, or returns <code>SASL_FAIL</code> in case of
	 *                error.
	 */
	public function step($response, &$message, &$interactions)
	{
		if(strlen($this->error))
			return(SASL_FAIL);
		return($this->_driver->step($this,$response,$message,$interactions));
	}
}

/**
 * XSaslDriver class.
 *
 * Each driver should extend the XSaslDriver
 *
 * @author Philippe Gaultier <pgaultier@gmail.com>
 * @version $Revision: 316 $  $Date: 2008-11-22 09:15:05 +0000 (Sat, 22 Nov 2008) $
 * @package XSystem.IO
 * @since 3.0
 */

abstract class XSaslDriver implements ISaslDriver {
	
	/**
	 * @var mixed credentials
	 */
	private $_credentials;
	/**
	 * @var mixed status
	 */
	private $_state;
	
	/**
	 * Constructor
	 */
	public function __construct()
	{
	}

	/**
	 * Initialise, this function should be overriden
	 * in the driver class
	 */
	public function initialize(&$client)
	{
		return(1);
	}
}

class XSaslDriverPlain extends XSaslDriver
{
	public function __construct()
	{
		parent::__construct();
		$this->_credentials = array();
		$this->_state=SASL_PLAIN_STATE_START;
	}

	public function start(&$client, &$message, &$interactions)
	{
		if($this->_state!=SASL_PLAIN_STATE_START)
		{
			$client->error="PLAIN authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->_credentials=array(
			"user"=>"",
			"password"=>"",
			"realm"=>"",
			"mode"=>""
		);
		$defaults=array(
			"realm"=>"",
			"mode"=>""
		);
		$status=$client->getCredentials($this->_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
		{
			switch($this->_credentials["mode"])
			{
				case SASL_PLAIN_EXIM_MODE:
					$message=$this->_credentials["user"]."\0".$this->_credentials["password"]."\0";
					break;
				case SASL_PLAIN_EXIM_DOCUMENTATION_MODE:
					$message="\0".$this->_credentials["user"]."\0".$this->_credentials["password"];
					break;
				default:
					$message=$this->_credentials["user"]."\0".$this->_credentials["user"].(strlen($this->_credentials["realm"]) ? "@".$this->_credentials["realm"] : "")."\0".$this->_credentials["password"];
					break;
			}
			$this->_state=SASL_PLAIN_STATE_DONE;
		}
		else
			unset($message);
		return($status);
	}

	public function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->_state)
		{
			case SASL_PLAIN_STATE_DONE:
				$client->error="PLAIN authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->error="invalid PLAIN authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

class XSaslDriverBasic extends XSaslDriver
{
	public function __construct()
	{
		parent::__construct();
		$this->_credentials = array();
		$this->_state=SASL_BASIC_STATE_START;
	}

	public function start(&$client, &$message, &$interactions)
	{
		if($this->_state!=SASL_BASIC_STATE_START)
		{
			$client->error="Basic authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->_credentials=array(
			"user"=>"",
			"password"=>""
		);
		$defaults=array(
		);
		$status=$client->getCredentials($this->_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
		{
			$message=$this->_credentials["user"].":".$this->_credentials["password"];
			$this->_state=SASL_BASIC_STATE_DONE;
		}
		else
			unset($message);
		return($status);
	}

	public function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->_state)
		{
			case SASL_BASIC_STATE_DONE:
				$client->error="Basic authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->error="invalid Basic authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

class XSaslDriverCramMd5 extends XSaslDriver
{
	public function __construct()
	{
		parent::__construct();
		$this->_credentials = array();
		$this->_state=SASL_CRAM_MD5_STATE_START;
	}

	public function hmacMd5($key,$text)
	{
		$key=(strlen($key)<64 ? str_pad($key,64,"\0") : substr($key,0,64));
		return(md5((str_repeat("\x5c", 64)^$key).pack("H32", md5((str_repeat("\x36", 64)^$key).$text))));
	}

	public function start(&$client, &$message, &$interactions)
	{
		if($this->_state!=SASL_CRAM_MD5_STATE_START)
		{
			$client->error="CRAM-MD5 authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->_credentials=array(
			"user"=>"",
			"password"=>""
		);
		$defaults=array();
		$status=$client->getCredentials($this->_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->_state=SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE;
		unset($message);
		return($status);
	}

	public function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->_state)
		{
			case SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE:
				$message=$this->_credentials["user"]." ".$this->hmacMd5($this->_credentials["password"], $response);
				$this->_state=SASL_CRAM_MD5_STATE_DONE;
				break;
			case SASL_CRAM_MD5_STATE_DONE:
				$client->error="CRAM-MD5 authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->error="invalid CRAM-MD5 authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

class XSaslDriverLogin extends XSaslDriver
{
	public function __construct()
	{
		parent::__construct();
		$this->_credentials = array();
		$this->_state=SASL_LOGIN_STATE_START;
	}

	public function start(&$client, &$message, &$interactions)
	{
		if($this->_state!=SASL_LOGIN_STATE_START)
		{
			$client->error="LOGIN authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->_credentials=array(
			"user"=>"",
			"password"=>"",
			"realm"=>""
		);
		$defaults=array(
			"realm"=>""
		);
		$status=$client->getCredentials($this->_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->_state=SASL_LOGIN_STATE_IDENTIFY_USER;
		unset($message);
		return($status);
	}

	public function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->_state)
		{
			case SASL_LOGIN_STATE_IDENTIFY_USER:
				$message=$this->_credentials["user"].(strlen($this->_credentials["realm"]) ? "@".$this->_credentials["realm"] : "");
				$this->_state=SASL_LOGIN_STATE_IDENTIFY_PASSWORD;
				break;
			case SASL_LOGIN_STATE_IDENTIFY_PASSWORD:
				$message=$this->_credentials["password"];
				$this->_state=SASL_LOGIN_STATE_DONE;
				break;
			case SASL_LOGIN_STATE_DONE:
				$client->error="LOGIN authentication was finished without success";
				break;
			default:
				$client->error="invalid LOGIN authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

class XSaslDriverDigest extends XSaslDriver
{
	public function __construct()
	{
		parent::__construct();
		$this->_credentials = array();
		$this->_state=SASL_DIGEST_STATE_START;
	}

	private function unq($string)
	{
		return(($string{0}=='"' && $string{strlen($string)-1}=='"') ? substr($string, 1, strlen($string)-2) : $string);
	}

	private function hash($data)
	{
		return md5($data);
	}

	private function KD($secret, $data)
	{
		return $this->hash($secret.':'.$data);
	}

	public function start(&$client, &$message, &$interactions)
	{
		if($this->_state!=SASL_DIGEST_STATE_START)
		{
			$client->error='Digest authentication state is not at the start';
			return(SASL_FAIL);
		}
		$this->credentials=array(
			'user'=>'',
			'password'=>'',
			'uri'=>'',
			'method'=>'',
			'session'=>''
		);
		$defaults=array();
		$status=$client->getCredentials($this->_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->_state=SASL_DIGEST_STATE_RESPOND_CHALLENGE;
		unset($message);
		return($status);
	}

	public function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->_state)
		{
			case SASL_DIGEST_STATE_RESPOND_CHALLENGE:
				$values=explode(',',$response);
				$parameters=array();
				for($v=0; $v<count($values); $v++)
					$parameters[strtok(trim($values[$v]), '=')]=strtok('');

				$message='username="'.$this->_credentials['user'].'"';
				if(!isset($parameters[$p='realm'])
				&& !isset($parameters[$p='nonce']))
				{
					$client->error='Digest authentication parameter '.$p.' is missing from the server response';
					return(SASL_FAIL);
				}
				$message.=', realm='.$parameters['realm'];
				$message.=', nonce='.$parameters['nonce'];
				$message.=', uri="'.$this->_credentials['uri'].'"';
				if(isset($parameters['algorithm']))
				{
					$algorithm=$this->unq($parameters['algorithm']);
					$message.=', algorithm='.$parameters['algorithm'];
				}
				else
					$algorithm='';

				$realm=$this->unq($parameters['realm']);
				$nonce=$this->unq($parameters['nonce']);
				if(isset($parameters['qop']))
				{
					switch($qop=$this->unq($parameters['qop']))
					{
						case "auth":
							$cnonce=$this->_credentials['session'];
							break;
						default:
							$client->error='Digest authentication quality of protection '.$qop.' is not yet supported';
							return(SASL_FAIL);
					}
				}
				$nc_value='00000001';
				if(isset($parameters['qop'])
				&& !strcmp($algorithm, 'MD5-sess'))
					$A1=$this->hash($this->_credentials['user'].':'. $realm.':'. $this->_credentials['password']).':'.$nonce.':'.$cnonce;
				else
					$A1=$this->_credentials['user'].':'. $realm.':'. $this->_credentials['password'];
				$A2=$this->_credentials['method'].':'.$this->_credentials['uri'];
				if(isset($parameters['qop']))
					$response=$this->KD($this->hash($A1), $nonce.':'. $nc_value.':'. $cnonce.':'. $qop.':'. $this->hash($A2));
				else
					$response=$this->KD($this->hash($A1), $nonce.':'. $this->hash($A2));
				$message.=', response="'.$response.'"';
				if(isset($parameters['opaque']))
					$message.=', opaque='.$parameters['opaque'];
				if(isset($parameters['qop']))
					$message.=', qop="'.$qop.'"';
				$message.=', nc='.$nc_value;
				if(isset($parameters['qop']))
					$message.=', cnonce="'.$cnonce.'"';
				$client->encodeResponse=0;
				$this->_state=SASL_DIGEST_STATE_DONE;
				break;
			case SASL_DIGEST_STATE_DONE:
				$client->error='Digest authentication was finished without success';
				return(SASL_FAIL);
			default:
				$client->error='invalid Digest authentication step state';
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

class XSaslDriverNtlm extends XSaslDriver
{
	public function __construct()
	{
		parent::__construct();
		$this->_credentials = array();
		$this->_state=SASL_NTLM_STATE_START;
	}

	public function initialize(&$client)
	{
		if(!function_exists($function="mcrypt_encrypt")
		|| !function_exists($function="mhash"))
		{
			$extensions=array(
				"mcrypt_encrypt"=>"mcrypt",
				"mhash"=>"mhash"
			);
			$client->error="the extension ".$extensions[$function]." required by the NTLM SASL client class is not available in this PHP configuration";
			return(0);
		}
		return(1);
	}

	private function asciiToUnicode($ascii)
	{
		for($unicode="",$a=0;$a<strlen($ascii);$a++)
			$unicode.=substr($ascii,$a,1).chr(0);
		return($unicode);
	}

	private function typeMsg1($domain,$workstation)
	{
		$domain_length=strlen($domain);
		$workstation_length=strlen($workstation);
		$workstation_offset=32;
		$domain_offset=$workstation_offset+$workstation_length;
		return(
			"NTLMSSP\0".
			"\x01\x00\x00\x00".
			"\x07\x32\x00\x00".
			pack("v",$domain_length).
			pack("v",$domain_length).
			pack("V",$domain_offset).
			pack("v",$workstation_length).
			pack("v",$workstation_length).
			pack("V",$workstation_offset).
			$workstation.
			$domain
		);
	}

	private function NTLMResponse($challenge,$password)
	{
		$unicode=$this->asciiToUnicode($password);
		$md4=mhash(MHASH_MD4,$unicode);
		$padded=$md4.str_repeat(chr(0),21-strlen($md4));
		$iv_size=mcrypt_get_iv_size(MCRYPT_DES,MCRYPT_MODE_ECB);
		$iv=mcrypt_create_iv($iv_size,MCRYPT_RAND);
		for($response="",$third=0;$third<21;$third+=7)
		{
			for($packed="",$p=$third;$p<$third+7;$p++)
				$packed.=str_pad(decbin(ord(substr($padded,$p,1))),8,"0",STR_PAD_LEFT);
			for($key="",$p=0;$p<strlen($packed);$p+=7)
			{
				$s=substr($packed,$p,7);
				$b=$s.((substr_count($s,"1") % 2) ? "0" : "1");
				$key.=chr(bindec($b));
			}
			$ciphertext=mcrypt_encrypt(MCRYPT_DES,$key,$challenge,MCRYPT_MODE_ECB,$iv);
			$response.=$ciphertext;
		}
		return $response;
	}

	private function typeMsg3($ntlm_response,$user,$domain,$workstation)
	{
		$domain_unicode=$this->asciiToUnicode($domain);
		$domain_length=strlen($domain_unicode);
		$domain_offset=64;
		$user_unicode=$this->asciiToUnicode($user);
		$user_length=strlen($user_unicode);
		$user_offset=$domain_offset+$domain_length;
		$workstation_unicode=$this->asciiToUnicode($workstation);
		$workstation_length=strlen($workstation_unicode);
		$workstation_offset=$user_offset+$user_length;
		$lm="";
		$lm_length=strlen($lm);
		$lm_offset=$workstation_offset+$workstation_length;
		$ntlm=$ntlm_response;
		$ntlm_length=strlen($ntlm);
		$ntlm_offset=$lm_offset+$lm_length;
		$session="";
		$session_length=strlen($session);
		$session_offset=$ntlm_offset+$ntlm_length;
		return(
			"NTLMSSP\0".
			"\x03\x00\x00\x00".
			pack("v",$lm_length).
			pack("v",$lm_length).
			pack("V",$lm_offset).
			pack("v",$ntlm_length).
			pack("v",$ntlm_length).
			pack("V",$ntlm_offset).
			pack("v",$domain_length).
			pack("v",$domain_length).
			pack("V",$domain_offset).
			pack("v",$user_length).
			pack("v",$user_length).
			pack("V",$user_offset).
			pack("v",$workstation_length).
			pack("v",$workstation_length).
			pack("V",$workstation_offset).
			pack("v",$session_length).
			pack("v",$session_length).
			pack("V",$session_offset).
			"\x01\x02\x00\x00".
			$domain_unicode.
			$user_unicode.
			$workstation_unicode.
			$lm.
			$ntlm
		);
	}

	public function start(&$client, &$message, &$interactions)
	{
		if($this->_state!=SASL_NTLM_STATE_START)
		{
			$client->error="NTLM authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->_credentials=array(
			"user"=>"",
			"password"=>"",
			"realm"=>"",
			"workstation"=>""
		);
		$defaults=array();
		$status=$client->getCredentials($this->_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->_state=SASL_NTLM_STATE_IDENTIFY_DOMAIN;
		unset($message);
		return($status);
	}

	public function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->_state)
		{
			case SASL_NTLM_STATE_IDENTIFY_DOMAIN:
				$message=$this->typeMsg1($this->_credentials["realm"],$this->_credentials["workstation"]);
				$this->_state=SASL_NTLM_STATE_RESPOND_CHALLENGE;
				break;
			case SASL_NTLM_STATE_RESPOND_CHALLENGE:
				$ntlm_response=$this->NTLMResponse(substr($response,24,8),$this->_credentials["password"]);
				$message=$this->typeMsg3($ntlm_response,$this->_credentials["user"],$this->_credentials["realm"],$this->_credentials["workstation"]);
				$this->_state=SASL_NTLM_STATE_DONE;
				break;
			case SASL_NTLM_STATE_DONE:
				$client->error="NTLM authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->error="invalid NTLM authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

/**
 * ISasl interface.
 *
 * This interface must be implemented by sasl clients.
 *
 * @author Philippe Gaultier <pgaultier@gmail.com>
 * @version $Revision: 316 $  $Date: 2008-11-22 09:15:05 +0000 (Sat, 22 Nov 2008) $
 * @package System
 * @since 3.0
 */
interface ISaslDriver
{
	/**
	 * @param X the configuration for the module
	 */
	public function initialize(&$saslClient);
	/**
	 * Process the initial authentication step initializing the driver 
	 * class that implements the first of the list of requested mechanisms 
	 * that is supported by this SASL client library implementation.
	 * 
	 * @param XSasl   saslClient
	 * @param string  message Return the initial message that should be sent to the
	 *                server to start the authentication dialog. If this value is
	 *                undefined, no message should be sent to the server.
	 * @param array   interactions Not yet in use. It is meant to provide context
	 *                information to interact with the end user.
	 * @return int    The function may return <code>SASL_CONTINUE</code> if it
	 *                could start one of the requested authentication mechanisms. It
	 *                may return <code>SASL_NOMECH</code> if it was not possible to start
	 *                any of the requested mechanisms. It returns <code>SASL_FAIL</code> or
	 *                other value in case of error.
	 * 
	 */
	public function start(&$saslClient, &$message, &$interactions);
	/**
	 * Process the authentication steps after the initial step,
	 * until the authentication iteration dialog is complete.
	 * 
	 * @param XSasl   saslClient
	 * @param string  response Pass the response returned by the server to the previous
	 *                step.
	 * @param string  response Return the message that should be sent to the server to
	 *                continue the authentication dialog. If this value is undefined,
	 *                message should be sent to the server.
	 * @param array   interactions Not yet in use. It is meant to provide context
	 *                information to interact with the end user.
	 * @return int    The function returns <code>SASL_CONTINUE</code> if step was
	 *                processed successfully, or returns <code>SASL_FAIL</code> in
	 *                case of error.
	 */
	public function step(&$saslClient, $response, &$message, &$interactions);
}
?>
